The base class libraries provide a class type named System.GC that allows you to programmatically interact with the garbage collector using a set of static members. Now, do be very aware that you will seldom (if ever) need to make use of this class directly in your code. Typically, the only time you will use the members of System.GC is when you are creating classes that make internal use of unmanaged resources. This could be the case if you are building a class that makes calls into the Windows C-based API using the .NET platform invocation protocol, or perhaps due to some very low level and complicated COM interop logic. Table 8-1 provides a rundown of some of the more interesting members (consult the .NET Framework 4.0 SDK documentation for complete details).
Table 8-1. Select Members of the System.GC Type
System.GC Member | Description |
---|---|
AddMemoryPressure() RemoveMemoryPressure() | Allows you to specify a numerical value that represents the calling object’s “urgency level” regarding the garbage collection process. Be aware that these methods should alter pressure in tandem and thus never remove more pressure than the total amount you have added. |
Collect() | Forces the GC to perform a garbage collection. This method has been overloaded to specify a generation to collect, as well as the mode of collection (via the GCCollectionMode enumeration). |
CollectionCount() | Returns a numerical value representing how many times a given generation has been swept. |
GetGeneration() | Returns the generation to which an object currently belongs. |
GetTotalMemory() | Returns the estimated amount of memory (in bytes) currently allocated on the managed heap. A Boolean parameter specifies whether the call should wait for garbage collection to occur before returning. |
MaxGeneration | Returns the maximum number of generations supported on the target system. Under Microsoft’s .NET 4.0, there are three possible generations: 0, 1, and 2. |
SuppressFinalize() | Sets a flag indicating that the specified object should not have its Finalize() method called. |
WaitForPendingFinalizers() | Suspends the current thread until all finalizable objects have been finalized. This method is typically called directly after invoking GC.Collect(). |
Note .NET 3.5 Service Pack 1 added the ability to receive notifications when a garbage collection is about to occur using a handful of new members. While this can be helpful in some limited scenarios, most applications will not need such functionality, and therefore I have chosen not to cover the details in this edition. If you are interested, look up “Garbage Collection Notifications” in the .NET Framework 4.0 SDK documentation.
To illustrate how the System.GC type can be used to obtain various garbage collection–centric details, consider the following Main() method, which makes use of several members of GC:
static void Main(string[] args) { Console.WriteLine("***** Fun with System.GC *****"); // Print out estimated number of bytes on heap. Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false)); // MaxGeneration is zero based, so add 1 for display purposes. Console.WriteLine("This OS has {0} object generations.\n", (GC.MaxGeneration + 1)); Car refToMyCar = new Car("Zippy", 100); Console.WriteLine(refToMyCar.ToString()); // Print out generation of refToMyCar object. Console.WriteLine("Generation of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); Console.ReadLine(); }
The base class libraries provide a class type named System.GC that allows you to programmatically interact with the garbage collector using a set of static members. Now, do be very aware that you will seldom (if ever) need to make use of this class directly in your code. Typically, the only time you will use the members of System.GC is when you are creating classes that make internal use of unmanaged resources. This could be the case if you are building a class that makes calls into the Windows C-based API using the .NET platform invocation protocol, or perhaps due to some very low level and complicated COM interop logic. Table 8-1 provides a rundown of some of the more interesting members (consult the .NET Framework 4.0 SDK documentation for complete details).
If you determine it may be beneficial to have the garbage collector check for unreachable objects, you could explicitly trigger a garbage collection, as follows:
static void Main(string[] args) { ... // Force a garbage collection and wait for // each object to be finalized. GC.Collect(); GC.WaitForPendingFinalizers(); ... }
When you manually force a garbage collection, you should always make a call to GC.WaitForPendingFinalizers(). With this approach, you can rest assured that all finalizable objects (described in the next section) have had a chance to perform any necessary cleanup before your program continues. Under the hood, GC.WaitForPendingFinalizers() will suspend the calling “thread” during the collection process. This is a good thing, as it ensures your code does not invoke methods on an object currently being destroyed!
The GC.Collect() method can also be supplied a numerical value that identifies the oldest generation on which a garbage collection will be performed. For example, to instruct the CLR to investigate only generation 0 objects, you would write the following:
static void Main(string[] args) { ... // Only investigate generation 0 objects. GC.Collect(0); GC.WaitForPendingFinalizers(); ... }
As well, the Collect() method can also be passed in a value of the GCCollectionMode enumeration as a second parameter, to fine-tune exactly how the runtime should force the garbage collection. This enum defines the following values:
public enum GCCollectionMode { Default, // Forced is the current default. Forced, // Tells the runtime to collect immediately! Optimized // Allows the runtime to determine // whether the current time is optimal to reclaim objects. }
As with any garbage collection, calling GC.Collect()promotes surviving generations. To illustrate, assume that our Main() method has been updated as follows:
static void Main(string[] args) { Console.WriteLine("***** Fun with System.GC *****"); // Print out estimated number of bytes on heap. Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false)); // MaxGeneration is zero based. Console.WriteLine("This OS has {0} object generations.\n", (GC.MaxGeneration + 1)); Car refToMyCar = new Car("Zippy", 100); Console.WriteLine(refToMyCar.ToString()); // Print out generation of refToMyCar. Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); // Make a ton of objects for testing purposes. object[] tonsOfObjects = new object[50000]; for (int i = 0; i < 50000; i++) tonsOfObjects[i] = new object(); // Collect only gen 0 objects. GC.Collect(0, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); // Print out generation of refToMyCar. Console.WriteLine("Generation of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); // See if tonsOfObjects[9000] is still alive. if (tonsOfObjects[9000] != null) { Console.WriteLine("Generation of tonsOfObjects[9000] is: {0}", GC.GetGeneration(tonsOfObjects[9000])); } else Console.WriteLine("tonsOfObjects[9000] is no longer alive."); // Print out how many times a generation has been swept. Console.WriteLine("\nGen 0 has been swept {0} times", GC.CollectionCount(0)); Console.WriteLine("Gen 1 has been swept {0} times", GC.CollectionCount(1)); Console.WriteLine("Gen 2 has been swept {0} times", GC.CollectionCount(2)); Console.ReadLine(); }
Here, we have purposely created a very large array of object types (50,000 to be exact) for testing purposes. As you can see from the output that follows, even though this Main() method only made one explicit request for a garbage collection (via the GC.Collect() method), the CLR performed a number of them in the background.
***** Fun with System.GC ***** Estimated bytes on heap: 70240 This OS has 3 object generations. Zippy is going 100 MPH Generation of refToMyCar is: 0 Generation of refToMyCar is: 1 Generation of tonsOfObjects[9000] is: 1 Gen 0 has been swept 1 times Gen 1 has been swept 0 times Gen 2 has been swept 0 times
At this point, I hope you feel more comfortable regarding the details of object lifetime. In the next section, we’ll examine the garbage collection process a bit further by addressing how you can build finalizable objects as well as disposable objects. Be very aware that the following techniques are useful only if you are building managed classes that maintain internal unmanaged resources.
Source Code The SimpleGC project is included under the Chapter 8 subdirectory.